// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System.Collections.Generic;
using System.Linq;
using System.Text;
using JetBrains.Annotations;
using SnapObjects.Commons;

namespace SnapObjects.Data.Oracle
{
    internal class OracleUpdateSqlGenerator : UpdateSqlGenerator
    {
        public OracleUpdateSqlGenerator(OracleSqlGenerationHelper oracleSqlGenerationHelper) :
            base(oracleSqlGenerationHelper)
        {
        }

        public override ResultSetMapping AppendRawUpdateSql([NotNull] StringBuilder sqlStringBuilder,
            [NotNull] string rawUpdateSql,
            int sqlPosition)
        {
            sqlStringBuilder.Append(rawUpdateSql);
            sqlStringBuilder.Append(this.SqlGenerationHelper.StatementTerminator).AppendLine();

            this.AppendOutputParamerter(null, sqlStringBuilder);

            return this.AppendSelectAffectedCountSql(sqlStringBuilder, null, null, 0);
        }

        public override ResultSetMapping AppendDeleteSql([NotNull] StringBuilder sqlStringBuilder,
            [NotNull] ModelUpdateInfo modelUpdateInfo,
            int sqlPosition)
        {
            Check.NotNull(sqlStringBuilder, nameof(sqlStringBuilder));
            Check.NotNull(modelUpdateInfo, nameof(modelUpdateInfo));

            var name = modelUpdateInfo.Table;
            var schema = modelUpdateInfo.Schema;
            var conditionOperations = modelUpdateInfo.ColumnModifications.Where(o => o.IsCondition).ToList();

            this.AppendDeleteSql(sqlStringBuilder, name, schema, conditionOperations);

            this.AppendOutputParamerter(modelUpdateInfo, sqlStringBuilder);

            return this.AppendSelectAffectedCountSql(sqlStringBuilder, name, schema, sqlPosition);
        }

        public override ResultSetMapping AppendInsertSql([NotNull] StringBuilder sqlStringBuilder,
            [NotNull] ModelUpdateInfo modelUpdateInfo,
            int sqlPosition)
        {
            Check.NotNull(sqlStringBuilder, nameof(sqlStringBuilder));
            Check.NotNull(modelUpdateInfo, nameof(modelUpdateInfo));

            var name = modelUpdateInfo.Table;
            var schema = modelUpdateInfo.Schema;
            var operations = modelUpdateInfo.ColumnModifications;

            var writeOperations = operations.Where(o => o.IsWrite).ToList();
            var readOperations = operations.Where(o => o.IsRead).ToList();

            this.AppendInsertSql(sqlStringBuilder, name, schema, writeOperations);

            if (readOperations.Count > 0)
            {
                modelUpdateInfo.BuildOutputParameters();

                var keyOperations = operations.Where(o => o.IsKey).ToList();

                return this.AppendSelectAffectedSql(sqlStringBuilder, name, schema, readOperations, keyOperations, sqlPosition);
            }
            else
            {
                sqlStringBuilder.Append("\n").Append(this.SqlGenerationHelper.StatementTerminator);

                return this.AppendSelectAffectedCountSql(sqlStringBuilder, name, schema, sqlPosition);
            }
        }

        public override ResultSetMapping AppendUpdateSql([NotNull] StringBuilder sqlStringBuilder, [NotNull] ModelUpdateInfo modelUpdateInfo, int sqlPosition)
        {
            Check.NotNull(sqlStringBuilder, nameof(sqlStringBuilder));
            Check.NotNull(modelUpdateInfo, nameof(modelUpdateInfo));

            var name = modelUpdateInfo.Table;
            var schema = modelUpdateInfo.Schema;
            var operations = modelUpdateInfo.ColumnModifications;

            var writeOperations = operations.Where(o => o.IsWrite).ToList();
            var conditionOperations = operations.Where(o => o.IsCondition).ToList();
            var readOperations = operations.Where(o => o.IsRead).ToList();

            if (writeOperations.Count > 0)
            {
                this.AppendUpdateSql(sqlStringBuilder, name, schema, writeOperations, conditionOperations);
            }

            this.AppendOutputParamerter(modelUpdateInfo, sqlStringBuilder);

            return this.AppendSelectAffectedCountSql(sqlStringBuilder, name, schema, sqlPosition);
        }

        protected override void AppendUpdateHeader(
           [NotNull] StringBuilder sqlStringBuilder,
           [NotNull] string name,
           [CanBeNull] string schema,
           [NotNull] IReadOnlyList<ColumnModification> operations)
        {
            Check.NotNull(sqlStringBuilder, nameof(sqlStringBuilder));
            Check.NotEmpty(name, nameof(name));
            Check.NotNull(operations, nameof(operations));

            sqlStringBuilder.Append("UPDATE ");
            this.SqlGenerationHelper.DelimitIdentifier(sqlStringBuilder, name, schema);
            sqlStringBuilder.Append(" SET ")
                .AppendValues(
                    operations,
                    this.SqlGenerationHelper,
                    (sb, o, helper) =>
                    {
                        helper.DelimitIdentifier(sb, o.MappedColumn);
                        sb.Append(" = ");
                        helper.GenerateParameterName(sb, o.ParameterName);
                    });
        }

        protected override void AppendInsertSql([NotNull] StringBuilder sqlStringBuilder,
            [NotNull] string name,
            [CanBeNull] string schema,
            [NotNull] IReadOnlyList<ColumnModification> writeOperations)
        {
            Check.NotNull(sqlStringBuilder, nameof(sqlStringBuilder));
            Check.NotEmpty(name, nameof(name));
            Check.NotNull(writeOperations, nameof(writeOperations));

            this.AppendInsertHeader(sqlStringBuilder, name, schema, writeOperations);
            this.AppendValuesHeader(sqlStringBuilder, writeOperations);
            this.AppendValues(sqlStringBuilder, writeOperations);
        }

        protected override void AppendInsertHeader([NotNull] StringBuilder sqlStringBuilder,
            [NotNull] string name,
            [CanBeNull] string schema,
            [NotNull] IReadOnlyList<ColumnModification> operations)
        {
            Check.NotNull(sqlStringBuilder, nameof(sqlStringBuilder));
            Check.NotEmpty(name, nameof(name));
            Check.NotNull(operations, nameof(operations));

            sqlStringBuilder.Append("INSERT INTO ");
            this.SqlGenerationHelper.DelimitIdentifier(sqlStringBuilder, name, schema);

            if (operations.Count > 0)
            {
                sqlStringBuilder
                    .Append(" (")
                    .AppendValues(operations,
                        this.SqlGenerationHelper,
                        (sb, o, helper) => helper.DelimitIdentifier(sb, o.MappedColumn))
                    .Append(")");
            }
        }

        protected override ResultSetMapping AppendSelectAffectedCountSql(StringBuilder sqlStringBuilder, string name, string schema, int sqlPosition)
        {
            return ResultSetMapping.RowCountAndLastInResultSet;
        }

        protected override ResultSetMapping AppendSelectAffectedSql([NotNull] StringBuilder sqlStringBuilder,
            [NotNull] string name,
            [CanBeNull] string schema,
            [NotNull] IReadOnlyList<ColumnModification> readOperations,
            [NotNull] IReadOnlyList<ColumnModification> conditionOperations, int sqlPosition)
        {
            var filed = new StringBuilder();
            var parameter = new StringBuilder();

            sqlStringBuilder.Append(" returning ");

            foreach (var readOperation in readOperations)
            {
                if (filed.Length > 0)
                {
                    filed.Append(",");
                    parameter.Append(",");
                }

                filed.Append(readOperation.MappedColumn);

                parameter.Append(this.SqlGenerationHelper.ParameterIdentifier)
                    .Append(readOperation.ParameterName);
            }

            sqlStringBuilder.Append(filed)
                .Append(" into ")
                .Append(parameter)
                .Append(this.SqlGenerationHelper.StatementTerminator);

            sqlStringBuilder.Append("\n");

            return ResultSetMapping.SelectAndLastInResultSet;
        }

        protected override void AppendWhereCondition([NotNull] StringBuilder sqlStringBuilder,
            [NotNull] ColumnModification columnModification,
            bool useOriginalValue)
        {
            Check.NotNull(sqlStringBuilder, nameof(sqlStringBuilder));
            Check.NotNull(columnModification, nameof(columnModification));

            this.SqlGenerationHelper.DelimitIdentifier(sqlStringBuilder, columnModification.MappedColumn);

            var parameterValue = useOriginalValue
                ? columnModification.OriginalValue
                : columnModification.CurrentValue;

            if (parameterValue == null)
            {
                sqlStringBuilder.Append(" IS NULL");

                sqlStringBuilder.Append(" AND ");

                this.SqlGenerationHelper.GenerateParameterName(sqlStringBuilder, useOriginalValue
                    ? columnModification.OriginalParameterName
                    : columnModification.ParameterName);

                sqlStringBuilder.Append(" IS NULL");
            }
            else
            {
                sqlStringBuilder.Append(" = ");
                this.SqlGenerationHelper.GenerateParameterName(sqlStringBuilder, useOriginalValue
                    ? columnModification.OriginalParameterName
                    : columnModification.ParameterName);
            }
        }

        protected override void AppendIdentityWhereCondition(StringBuilder sqlStringBuilder, ColumnModification columnModification)
        {
        }

        protected override void AppendRowsAffectedWhereCondition(StringBuilder sqlStringBuilder, int expectedRowsAffected)
        {
        }

        private void AppendOutputParamerter(ModelUpdateInfo modelUpdateInfo,
            StringBuilder sqlStringBuilder)
        {
            var parameterName = "";

            if (modelUpdateInfo != null)
            {
                modelUpdateInfo.BuildOutputParameters();

                parameterName = modelUpdateInfo.OutputParameters
                   .Where(m => m.Value == false)
                   .FirstOrDefault()
                   .Key.ParameterName;
            }
            else
            {
                parameterName = this.SqlGenerationHelper.OutputParameterPlacehold;
            }

            sqlStringBuilder
            .Append(this.SqlGenerationHelper.ParameterIdentifier)
            .Append(parameterName)
            .Append(":=")
            .Append("sql%rowcount")
            .Append(this.SqlGenerationHelper.StatementTerminator)
            .Append("\n");
        }
    }
}
